﻿using System.Collections.Generic;
using Microsoft.VisualStudio.Text;
using Red.Scripts.Defines.Utilities;

namespace Language.Stub
{
    public partial class Database
    {
        #region Public Methods

        #region Initialisation

        public void Initialize( comm.ScriptDatabase database )
        {
            foreach ( comm.EnumStub source in database.enums )
            {
                File file = GetFile( source.context.detailed.standard );

                Enum target = new Enum( source );
                file.Add( target );
            }

            foreach ( comm.ClassStub source in database.types )
            {
                File file = GetFile( source.context.detailed.standard );

                Class target = new Class( source );
                file.Add( target );
            }

            foreach ( comm.FunctionStub source in database.functions )
            {
                File file = GetFile( source.context.detailed.standard );

                Function target = new Function( source );
                file.Add( target );
            }

            foreach ( comm.CommentStub source in database.comments )
            {
                File file = GetFile( source.context.standard );

                Comment target = new Comment( source );
                file.Add( target );
            }

            foreach ( comm.LiteralStub source in database.literals )
            {
                File file = GetFile( source.context.standard );

                Literal target = new Literal( source );
                file.Add( target );
            }
        }

        internal void InitializationComplete()
        {
        }

        internal void Clear()
        {
            FileByPath.Clear();
        }

        #endregion Initialisation

        #region File Changed

        public void Add( string filePath, comm.ScriptDatabase database, ITextSnapshot snapshot, bool notifyListeners )
        {
            File file = new File( filePath, snapshot );
            FileByPath[ filePath ] = file;

            foreach ( comm.EnumStub source in database.enums )
            {
                Enum target = new Enum( source );
                file.Add( target );
            }

            foreach ( comm.ClassStub source in database.types )
            {
                Class target = new Class( source );
                file.Add( target );
            }

            foreach ( comm.FunctionStub source in database.functions )
            {
                Function target = new Function( source );
                file.Add( target );
            }

            foreach ( DetailedContext context in file.Stubs.Values )
            {
                Class classStub;
                Function funcStub;
                if ( ( classStub = context as Class ) != null )
                {
                    AddSupplimentaryTokens( classStub, file );
                }
                else if ( ( funcStub = context as Function ) != null )
                {
                    AddSupplimentaryTokens( funcStub, file );
                }
            }

            foreach ( comm.CommentStub source in database.comments )
            {
                Comment comment = new Comment( source );
                file.Add( comment );
            }

            foreach ( comm.LiteralStub source in database.literals )
            {
                Literal literal = new Literal( source );
                file.Add( literal );
            }

            if ( notifyListeners )
                Updated?.Invoke( this, new SnapshotUpdatedArgs( filePath, snapshot ) );
        }

        public void Add( string filePath, comm.SccCompileFunctionsResponse functionsResponse, ITextSnapshot snapshot )
        {
            File file = GetFile( filePath );

            file.Tokens.Clear();
            file.ScopedTokens.Clear();

            foreach ( comm.ScriptToken source in functionsResponse.tokens )
            {
                AddToken( source, file );
            }

            foreach( comm.DetailedContext source in functionsResponse.breakpoints )
            {
                DetailedContext target = new DetailedContext();
                target.Set( source );
                file.Breakpoints[ target.Position.End ] = target;
            }

            Updated?.Invoke( this, new SnapshotUpdatedArgs( filePath, snapshot ) );
        }

        #endregion File Changed

        #endregion Public Methods

        #region Private Methods

        #region Utility

        private File GetFile( string path )
        {
            File file;
            if ( !FileByPath.TryGetValue( path, out file ) )
            {
                file = new File( path, null );

                FileByPath.Add( path, file );
            }

            return file;
        }

        private File GetFile( comm.StandardContext context )
        {
            return GetFile( context.file );
        }

        #endregion Utility

        #region Supplimentary Tokens

        private void AddSupplimentaryTokens( Class stub, File file )
        {
            AddSupplimentaryToken( stub, file );
            AddSupplimentaryTokensForProperties( stub.Properties, file );

            foreach ( Function function in stub.Functions )
            {
                AddSupplimentaryTokens( function, file );
            }
        }

        private void AddSupplimentaryTokens( Function stub, File file )
        {
            AddSupplimentaryToken( stub, file );
            AddSupplimentaryTokensForProperties( stub.Locals, file );
            AddSupplimentaryTokensForProperties( stub.Parameters, file );
        }

        private void AddSupplimentaryTokensForProperties( IEnumerable<Property> properties, File file )
        {
            foreach ( Property property in properties )
            {
                AddSupplimentaryToken( property, file );
            }
        }

        private void AddSupplimentaryToken( TypedContext context, File file )
        {
            Token token = CreateSupplimentaryToken( context );

            // Due to the fact that we can group multiple property declarations into a single type
            // i.e. var banana, pear, apple : Fruit;
            // We must accept that there will be duplicate tokens in this particular list (and ignore them)
            if ( token != null )
                file.SupplementaryTokens[ token.Position.End ] = token;
        }

        private Token CreateSupplimentaryToken( TypedContext stub )
        {
            File definitionFile;
            if ( !FileByPath.TryGetValue( stub.TypeDefinition.File, out definitionFile ) )
                return null;

            DetailedContext definition;
            if ( !definitionFile.Stubs.TryGetValue( stub.TypeDefinition.Position.End, out definition ) )
                return null;

            Token token = new Token();

            token.File = stub.File;
            token.Position = stub.TypeDeclaration.Position;

            token.Declaration = definition;

            return token;
        }

        #endregion Supplimentary Tokens

        #region Tokens

        /// <summary>
        /// When resolving enum consts, the syntax tree will swallow the type token and leave us just with the option
        /// So we're compensating for that here
        /// </summary>
        private void CreateTypeTokenForOption( Option declaration, comm.ScriptToken source, File file )
        {
            int parentLength = declaration.Parent.Position.End - declaration.Parent.Position.Start;

            comm.ScriptToken fakeType = new comm.ScriptToken();
            fakeType.use.file = source.use.file;

            // -1 for the preceeding '.'
            fakeType.use.position.start = source.use.position.start - ( parentLength + 1 );
            fakeType.use.position.end = fakeType.use.position.start + parentLength;

            fakeType.declaration.file = declaration.Parent.File;
            fakeType.declaration.position.start = declaration.Parent.Position.Start;
            fakeType.declaration.position.end = declaration.Parent.Position.End;

            AddToken( fakeType, file );
        }

        private ScopedToken CreateScopedToken( comm.PositionContext scope, File file )
        {
            if( scope.start == -1 )
            {
                return null;
            }

            ScopedToken token = new ScopedToken();
            token.Scope.Set( scope );

            file.ScopedTokens.Add( token.Scope.End, token );

            return token;
        }

        private void AddToken( comm.ScriptToken source, File file )
        {
            File declarationFile;
            if ( FileByPath.TryGetValue( source.declaration.file, out declarationFile ) )
            {
                int index = declarationFile.Stubs.LowerBound( source.declaration.position.start );

                if ( index == -1 || index >= declarationFile.Stubs.Count )
                    return;

                DetailedContext declaration = declarationFile.Stubs.Values[ index ];
                if ( declaration.Position.Start == source.declaration.position.start && declaration.Position.End == source.declaration.position.end )
                {
                    Token target;

                    if ( declaration is Function )
                    {
                        target = CreateScopedToken( source.scope, file );
                        if ( target == null )
                            return;
                    }
                    else
                    {
                        target = new Token();
                    }

                    target.File = source.use.file;
                    target.Position.Set( source.use.position );

                    if ( declaration is Option )
                    {
                        CreateTypeTokenForOption( (Option)declaration, source, file );
                    }

                    target.Declaration = declaration;
                    file.Tokens.Add( target.Position.End, target );
                }
            }
        }

        #endregion Tokens

        #endregion Private Methods
    }
}
